<?php
class ModulePaymentFacebook extends ModulePayment
{

    const PAYMENT_STATUS_DECLINE    = 3;
    const PAYMENT_STATUS_REFUND     = 4;
    const PAYMENT_STATUS_CHARGEBACK = 5;
    const PAYMENT_STATUS_DISPUTE    = 6;

    /**
     * @var ModuleSnsFacebook
     */
    private $moduleSns;

    /**
     * @see ModulePayment::__construct()
     */
    public function __construct($userId, $platform)
    {
        parent::__construct($userId, $platform);
        $this->moduleSns = ModuleSnsFactory::init(null, ModuleSnsConst::PFFB);
    }

    /**
     * Get payment def info.
     *
     * @param int $userId
     * @param int $paymentDefId
     * @param string $reqSerial
     * <pre>
     * a random number, used to refresh the production graph xml, could be generated by JavaScript:
     * new Date().getTime() + '' + (Math.random() * 10000 + '')
     * </pre>
     * @param string $lang
     * @return string $info
     */
    public function getPaymentInfo($userId, $paymentDefId, $reqSerial, $lang)
    {
        $appConfig = SzConfig::get()->loadAppConfig('app');
        $paymentSetting = SzConfig::get()->loadAppConfig(ModulePaymentConst::CONFIG_PAYMENT, $paymentDefId, true);

        $develop = 'master';
        $paramUrl = '';
        if ($appConfig['ENV'] == 'DEV') {
            $develop = (SzParam::getReqParam('dev')) ? SzParam::getReqParam('dev') : 'master';
            $paramUrl = '&dev=' . $develop;
        }

        $requestUrl = 'https://' . $appConfig['WEB_HOST'] . '/apps/' . $develop . '/' . $appConfig['PHP_VER'] . '/www';
        $icon   = $requestUrl . '/img/' . $lang . '/payment/' . $paymentSetting['icon'];
        $url    = $requestUrl . '/index.php?token=52d0ee3c031dd&user_id=' . $userId . '&act=product&id=' . $paymentDefId . '&serial=' . $reqSerial . $paramUrl;

        $info =
'<!DOCTYPE html>
<html>
<head prefix=
    "og: http://ogp.me/ns#
    fb: http://ogp.me/ns/fb#
    product: http://ogp.me/ns/product#">
   <meta property="og:type"                content="og:product" />
   <meta property="og:title"               content="' . $paymentSetting['name'] . '" />
   <meta property="og:image"               content="' . $icon . '" />
   <meta property="og:description"         content="' . $paymentSetting['desc'] . '" />
   <meta property="og:url"                 content="' . $url . '" />
   <meta property="product:price:amount"   content="' . $paymentSetting['price'] . '"/>
   <meta property="product:price:currency" content="USD"/>
</head>
</html>';

        return $info;
    }

    /**
     * @see ModulePayment::completeOrder()
     */
    public function completeOrder()
    {
        if (SzParam::getReqParam('hub_verify_token')) {
            // if $_REQUEST contains hub_verify_token, it's a test call to set token, shall be handled here
            $payTokenConfig = ModuleSnsFactory::init(null, ModuleSnsConst::PFFB)->loadSnsSetting('payToken');
            if ($payTokenConfig['value'] == SzParam::getReqParam('hub_verify_token')) {
                return SzParam::getReqParam('hub_challenge');
            }
        } else if (SzParam::getReqParam('hub_challenge')) {
            // test callback, just echo hub_challenge
            return SzParam::getReqParam('hub_challenge');
        }

        $phpInput = file_get_contents('php://input');
        if (!$_POST && $phpInput) {
            // it's a realtime update
            $result = $this->completeOrderRealtimeUpdate($phpInput, json_decode($phpInput, true));
        } else {
            // it's a js callback
            $result = $this->completeOrderJsUpdate();
        }

        return $result;
    }

    /**
     * Complete the payment from facebook realtime update.
     *
     * @param string $fbInput data from 'php://input'
     * @param array $decodedFbInput json decoded data
     * @return string | array(
     * int $userId,
     * ModulePaymentOrderVo $order
     * )
     */
    private function completeOrderRealtimeUpdate($fbInput, $decodedFbInput)
    {
        // check the decoded structure
        if (!(isset($decodedFbInput['object'])
            && isset($decodedFbInput['entry'])
            && $decodedFbInput['object'] == 'payments')) {
            SzLogger::get()->warn('[ModulePaymentFacebook] Realtime update: invalid facebook input format'); // invalid structure
            return 'invalid';
        }

        // check the verify signature
        $callbackSignature = SzUtility::explodeWithTrim('=', $_SERVER['HTTP_X_HUB_SIGNATURE']);
        if (!$this->moduleSns->validateRealtimeUpdateSignature($fbInput, $callbackSignature[1])) {
            return 'invalid'; // invalid signature
        }

        // fetch payment info from facebook
        $paymentId = $decodedFbInput['entry'][0]['id'];
        /**
         * <pre>
         *  array(
         *      "id" => 691281607669304,
         *      "user" => array("id" => "863704833648830", 'name' => "Jonathan Dai"),
         *      "application" => array("name" => "Flower Ville", "namespace" => "flower-ville", "id" => "1477815619163875"),
         *      "actions" => array(
         *          array(
         *                  "type" => "charge",
         *                  "status" => "completed",
         *                  "currency" => "EUR",
         *                  "amount" => "10.00",
         *                  "time_created" => "2015-06-25T10:08:22+0000",
         *                  "time_updated" => "2015-06-25T10:08:22+0000"
         *          ), ...
         *      ),
         *      "refundable_amount" => array("currency" => "EUR", "amount" => "10.00"),
         *      "items" => array(
         *          array(
         *              "type" => "IN_APP_PURCHASE",
         *              "product" => PRODUCT_URL,
         *              "quantity" => 111
         *          ), ..
         *      )
         *      "country" => "CN",
         *      "request_id" => "326358_14352268715622688.663948792964",
         *      "created_time" => "2015-06-25T10:08:22+0000",
         *      "test" => 1,
         *      "payout_foreign_exchange_rate" => 1.1043549,
         *      "tax" => "not_taxed",
         *      "tax_country" => "CN",
         *  )
         * </pre>
         */
        $paymentInfo = $this->moduleSns->getPaymentInfo($paymentId);
        if (!$paymentInfo) {
            return 'invalid'; // empty payment info
        }

        // fetch order info from game database
        // $isTest = $paymentInfo['test']; // can be used to log whether it's test call
        list($orderId) = explode('_', $paymentInfo['request_id']); // {$paymentOrderId}_{$serial} , since $paymentOrderId is reused in our database, but FB order id cannot be reused, we have to add a serial number after it with _

        $platformId = $paymentInfo['user']['id'];
        $userId = $this->moduleSns->portPlayerUserId($platformId);
        if (!$userId) {
            SzLogger::get()->error('[ModulePaymentFacebook] Realtime update: no user id got from payment info'); // no user id data found
            return 'invalid';
        }
        /* @var ModulePaymentOrderVoList $orderList */
        $orderList = SzPersister::get()->getVoList(null, ModulePaymentConst::ORM_NAME_PAYMENT_ORDER, $userId);
        /* @var ModulePaymentOrderVo $order */
        $order = $orderList->getElement($orderId, false);
        if (!$order) {
            SzLogger::get()->error('[ModulePaymentFacebook] Realtime update: no order data, orderId: ' . $orderId); // no order data found
            return 'invalid';
        }

        // handle order
        $facebookPaymentStatus = ModulePaymentConst::STATUS_INITIALIZED; // set payment status to init
        $paymentSetting = SzConfig::get()->loadAppConfig(ModulePaymentConst::CONFIG_PAYMENT, $order->getPaymentDefId(), true);
        if ($decodedFbInput['entry'][0]['changed_fields'][0] == 'actions') { // normal order: changed fields is actions

            // loop all the actions in payment info, check & determine the final payment status
            foreach ($paymentInfo['actions'] as $paymentAction) {
                switch ($paymentAction['type']) {
                    case 'charge':
                        if ($facebookPaymentStatus < ModulePaymentConst::STATUS_COMPLETED // payment status not completed yet
                            && $paymentAction['status'] == 'completed') { // status in payment info completed
                            $facebookPaymentStatus = ModulePaymentConst::STATUS_COMPLETED;
                        }
                        break;
                    case 'decline':
                        if ($facebookPaymentStatus < self::PAYMENT_STATUS_DECLINE) {
                            $facebookPaymentStatus = self::PAYMENT_STATUS_DECLINE;
                        }
                        break;
                    case 'refund':
                        if ($facebookPaymentStatus < self::PAYMENT_STATUS_REFUND) {
                            $facebookPaymentStatus = self::PAYMENT_STATUS_REFUND;
                        }
                        break;
                    case 'chargeback':
                        if ($facebookPaymentStatus < self::PAYMENT_STATUS_CHARGEBACK) {
                            $facebookPaymentStatus = self::PAYMENT_STATUS_CHARGEBACK;
                        }
                        break;
                    default:
                        break;
                }
            }
            if ($facebookPaymentStatus == ModulePaymentConst::STATUS_COMPLETED // payment status is completed & has no further dispute status
                && $order->getStatus() < ModulePaymentConst::STATUS_COMPLETED) { // order status in db not completed yet
                $paymentItemInfo = array_shift($paymentInfo['items']);
                $paymentPrice = $paymentSetting['price'] * $paymentItemInfo['quantity'];
                $order = $this->completeOrderAndSupplyResources($userId, $order, $paymentSetting, $paymentItemInfo['quantity']);
                $order->setPlatformToken($paymentId);
                $order->setPrice($paymentPrice);
                $orderList->updateElement($order->getOrderId(), $order);
                SzPersister::get()->setVoList($orderList);
                SzLogger::get()->debug('[ModulePaymentFacebook] Realtime update: payment completed normally');
            } else if ($facebookPaymentStatus == ModulePaymentConst::STATUS_COMPLETED // payment status is completed & has no further dispute status
                && $order->getStatus() == ModulePaymentConst::STATUS_COMPLETED) { // order status already completed
                SzLogger::get()->info('[ModulePaymentFacebook] Realtime update: payment already completed');
            } else if ($facebookPaymentStatus > ModulePaymentConst::STATUS_COMPLETED // payment status has dispute status
                && $order->getStatus() < $facebookPaymentStatus) { // order status in db not updated
                $order->setStatus($facebookPaymentStatus);
                $orderList->updateElement($order->getOrderId(), $order);
                SzPersister::get()->setVoList($orderList);
                SzLogger::get()->debug('[ModulePaymentFacebook] Realtime update: payment has dispute status: ' . $facebookPaymentStatus);
            }

        } else { // dispute payment

            $order->setStatus(self::PAYMENT_STATUS_DISPUTE);
            if ($paymentInfo['disputes']) {
                $order->setExtra($paymentInfo['disputes']); // note: $paymentInfo['disputes'] is an array
            }
            $orderList->updateElement($order->getOrderId(), $order);
            SzPersister::get()->setVoList($orderList);
            SzLogger::get()->warn('[ModulePaymentFacebook] Realtime update: dispute payment, info: ' . json_encode($paymentInfo['disputes']));

        }

        return array($userId, $order, $paymentSetting);
    }

    /**
     * Complete the payment from js callback.
     *
     * @return string|array string invalid or array of payment def info
     */
    private function completeOrderJsUpdate()
    {
        /**
         * @var Facebook\Entities\SignedRequest $facebookSdk
         */
        $facebookSdk = $this->moduleSns->getPlatformSdk();

        $signedReq = SzParam::getReqParam('signed_request');
        $decodedSignedReq = $facebookSdk::parse($signedReq);
        /**
         * array (
         *     'payment_id' => '459942914136109',
         *     'amount' => '10.00',
         *     'currency' => 'USD',
         *     'quantity' => '1',
         *     'request_id' => '36',
         *     'status' => 'completed',
         *     'signed_request' => 'ncvciolaGMGG9a3HoSOcPNDKxsT8BCMqDBBzsQuWajQ.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImFtb3VudCI6IjEwLjAwIiwiY3VycmVuY3kiOiJVU0QiLCJpc3N1ZWRfYXQiOjE0MDAxMzE3NTMsInBheW1lbnRfaWQiOjQ1OTk0MjkxNDEzNjEwOSwicXVhbnRpdHkiOiIxIiwicmVxdWVzdF9pZCI6IjM2Iiwic3RhdHVzIjoiY29tcGxldGVkIn0',
         * )
         */
        $paymentData = SzParam::getReqParam('res');
        /**
         * array (
         *     'algorithm' => 'HMAC-SHA256',
         *     'amount' => '10.00',
         *     'currency' => 'USD',
         *     'issued_at' => 1400131753,
         *     'payment_id' => 459942914136109,
         *     'quantity' => '1',
         *     'request_id' => '36',
         *     'status' => 'completed',
         * )
         */
        $decodedPaymentData = $facebookSdk::parse($paymentData['signed_request']);

        if (isset($paymentData['payment_id'])
            && isset($decodedPaymentData['payment_id'])
            && $paymentData['payment_id'] == $decodedPaymentData['payment_id']
            && $paymentData['request_id'] == $decodedPaymentData['request_id']
            && $paymentData['status'] == $decodedPaymentData['status']
            && $decodedPaymentData['status'] == 'completed'
        ) {

            // normal payment info, validated
            $platformId = $decodedSignedReq['user_id'];
            list($orderId) = explode('_', $decodedPaymentData['request_id']); // {$paymentOrderId}_{$serial}
            $userId = $this->moduleSns->portPlayerUserId($platformId);
            if (!$userId) {
                SzLogger::get()->error('[ModulePaymentFacebook] Js callback: no user id got from payment info'); // no user id data found
                return 'invalid';
            }
            /* @var ModulePaymentOrderVoList $orderList */
            $orderList = SzPersister::get()->getVoList(null, ModulePaymentConst::ORM_NAME_PAYMENT_ORDER, $userId);
            /* @var ModulePaymentOrderVo $order */
            $order = $orderList->getElement($orderId, false);
            if (!$order) {
                SzLogger::get()->error('[ModulePaymentFacebook] Js callback: no order data, orderId: ' . $orderId); // no order data found
                return 'invalid';
            }

            $paymentSetting = SzConfig::get()->loadAppConfig(ModulePaymentConst::CONFIG_PAYMENT, $order->getPaymentDefId(), true);
            if ($order->getStatus() < ModulePaymentConst::STATUS_COMPLETED) { // order status in db not completed yet
                $paymentPrice = $paymentSetting['price'] * $decodedPaymentData['quantity'];
                $order = $this->completeOrderAndSupplyResources($userId, $order, $paymentSetting, $decodedPaymentData['quantity']);
                $order->setPlatformToken($paymentData['payment_id']);
                $order->setPrice($paymentPrice);
                $orderList->updateElement($order->getOrderId(), $order);
                SzPersister::get()->setVoList($orderList);
                SzLogger::get()->debug('[ModulePaymentFacebook] Js callback: payment completed normally');
            } else if ($order->getStatus() == ModulePaymentConst::STATUS_COMPLETED) { // order status already completed
                SzLogger::get()->info('[ModulePaymentFacebook] Js callback: payment already completed');
            }

            return array($userId, $order, $paymentSetting);

        } else {

            // invalid payment info
            SzLogger::get()->error('[ModulePaymentFacebook] Js callback: invalid callback params');
            return 'invalid';

        }

    }

    /**
     * @see ModulePayment::completeOrderReturnValue()
     *
     * @param array $result
     * <pre>
     *  (string)$result
     *      or
     *  array(
     *      (int)$userId,
     *      (ModulePaymentOrderVo)$order,
     *      (array)$paymentSetting
     *  )
     * </pre>
     * @return mixed $result
     * <pre>
     *  (string)$result
     *      or
     *  (ModulePaymentOrderVo)$order
     * </pre>
     */
    public function completeOrderReturnValue($result)
    {
        if (is_array($result)) {
            /**
             * @var ModulePaymentOrderVo $order
             */
            list(, $order) = $result;
            return $order;
        } else {
            return $result;
        }
    }

    /**
     * @see ModulePayment::repairOrder()
     */
    public function repairOrder($userId, $orderInfo)
    {
        // facebook has all order repair logic in realtime update, so there is no implementation in this function
    }
}